use std::convert::Infallible;

use reqwest::{
    RequestBuilder,
    multipart::{Form, Part},
};
use thiserror::Error;

pub trait ParamAppender<Builder>: Sized {
    type Error: std::error::Error + 'static;

    fn append_param(self, builder: Builder) -> Result<Builder, Self::Error>;
}

impl ParamAppender<RequestBuilder> for String {
    type Error = Infallible;

    fn append_param(self, builder: RequestBuilder) -> Result<RequestBuilder, Self::Error> {
        let builder = builder.body(self);
        Ok(builder)
    }
}

impl ParamAppender<RequestBuilder> for () {
    type Error = Infallible;

    fn append_param(self, builder: RequestBuilder) -> Result<RequestBuilder, Self::Error> {
        Ok(builder)
    }
}

#[derive(Debug, Error)]
#[error(transparent)]
pub enum ReqwestAppendParamError {
    #[error(transparent)]
    ReqwestError(#[from] reqwest::Error),

    #[error("{0}")]
    Other(Box<dyn std::error::Error>),
}

macro_rules! impl_param_appender {
    (
        [$($var:ident $ty:ident),*]
    ) => {
        impl<$($ty,)*> ParamAppender<reqwest::RequestBuilder> for ($($ty,)*)
        where
            $($ty: ParamAppender<reqwest::RequestBuilder>,)*
        {
            type Error = ReqwestAppendParamError;

            fn append_param(self, builder: reqwest::RequestBuilder) -> Result<reqwest::RequestBuilder, Self::Error> {
                let ($($var,)*) = self;
                $(
                    let builder = $var.append_param(builder).map_err(|e| ReqwestAppendParamError::Other(Box::new(e)))?;
                )*
                Ok(builder)
            }
        }
    };
}

macro_rules! all_the_tuples {
    ($name:ident) => {
        $name!([t1 T1]);
        $name!([t1 T1, t2 T2]);
        $name!([t1 T1, t2 T2, t3 T3]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15]);
        $name!([t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16]);
    };
}

all_the_tuples!(impl_param_appender);

pub trait FormParts {
    fn form_parts(self, form: Form, name: String) -> Form;
}

macro_rules! impl_form_parts {
    ($ty: ty) => {
        impl FormParts for $ty {
            fn form_parts(self, form: Form, name: String) -> Form {
                form.part(name, Part::text(self.to_string()))
            }
        }
    };
}

impl_form_parts!(i8);
impl_form_parts!(i16);
impl_form_parts!(i32);
impl_form_parts!(i64);
impl_form_parts!(i128);
impl_form_parts!(isize);
impl_form_parts!(u8);
impl_form_parts!(u16);
impl_form_parts!(u32);
impl_form_parts!(u64);
impl_form_parts!(u128);
impl_form_parts!(usize);
impl_form_parts!(f32);
impl_form_parts!(f64);
impl_form_parts!(bool);
impl_form_parts!(char);

impl<T: FormParts> FormParts for Vec<T> {
    fn form_parts(self, mut form: Form, name: String) -> Form {
        for item in self {
            form = T::form_parts(item, form, name.clone());
        }
        form
    }
}

impl<T: FormParts> FormParts for Option<T> {
    fn form_parts(self, form: Form, name: String) -> Form {
        match self {
            Some(value) => value.form_parts(form, name),
            None => form,
        }
    }
}

impl FormParts for String {
    fn form_parts(self, form: Form, name: String) -> Form {
        form.part(name, Part::text(self))
    }
}
